Skip to content

utils: use CertUtils.generateRandomKeyPair to create SSH keypair#12708

Draft
weizhouapache wants to merge 4 commits intoapache:4.22from
weizhouapache:4.22-create-ssh-keypair
Draft

utils: use CertUtils.generateRandomKeyPair to create SSH keypair#12708
weizhouapache wants to merge 4 commits intoapache:4.22from
weizhouapache:4.22-create-ssh-keypair

Conversation

@weizhouapache
Copy link
Member

Description

This PR replaces the ssk key generation by jsch with CertUtils.generateRandomKeyPair

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • Build/CI
  • Test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

Bug Severity

  • BLOCKER
  • Critical
  • Major
  • Minor
  • Trivial

Screenshots (if appropriate):

How Has This Been Tested?

How did you try to break this feature and the system with this change?

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the JSch library with the CertUtils utility for SSH key pair generation in the SSHKeysHelper class. The change modernizes the codebase by consolidating cryptographic operations under the already-used CertUtils class (which uses BouncyCastle) and removes a dependency on the JSch library.

Changes:

  • Replaced JSch-based key generation with CertUtils.generateRandomKeyPair in the constructor
  • Implemented manual SSH public key format encoding (ssh-rsa format with base64-encoded components)
  • Replaced JSch's key serialization methods with CertUtils.privateKeyToPem for private key export
  • Removed JSch dependency from both utils/pom.xml and the root pom.xml

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
utils/src/main/java/com/cloud/utils/ssh/SSHKeysHelper.java Replaces JSch key generation and serialization with CertUtils methods; implements SSH public key encoding manually
utils/pom.xml Removes JSch dependency from the utils module
pom.xml Removes JSch version property and dependency management entry from root POM

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 109 to 126
public String getPublicKey() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
keyPair.writePublicKey(baos, "");
try {
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();

ByteArrayOutputStream buffer = new ByteArrayOutputStream();

writeString(buffer,"ssh-rsa");
writeBigInt(buffer, rsaPublicKey.getPublicExponent());
writeBigInt(buffer, rsaPublicKey.getModulus());

return baos.toString();
String base64 = Base64.encodeBase64String(buffer.toByteArray());

return "ssh-rsa " + base64;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new implementation of getPublicKey that uses CertUtils.generateRandomKeyPair and manually constructs the SSH public key format is not covered by tests. The existing tests only verify static methods (getPublicKeyFromKeyMaterial and getPublicKeyFingerprint). Consider adding a test that creates an SSHKeysHelper instance, generates a key pair, retrieves the public key, and validates that it can be correctly parsed back and used for encryption (similar to what RSAHelper.encryptWithSSHPublicKey does).

Copilot uses AI. Check for mistakes.
@weizhouapache
Copy link
Member Author

@blueorangutan package

@blueorangutan
Copy link

@weizhouapache a [SL] Jenkins job has been kicked to build packages. It will be bundled with KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan
Copy link

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 16945

@weizhouapache
Copy link
Member Author

@blueorangutan test

@blueorangutan
Copy link

@weizhouapache a [SL] Trillian-Jenkins test job (ol8 mgmt + kvm-ol8) has been kicked to run smoke tests

@blueorangutan
Copy link

[SF] Trillian test result (tid-15529)
Environment: kvm-ol8 (x2), zone: Advanced Networking with Mgmt server ol8
Total time taken: 56170 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr12708-t15529-kvm-ol8.zip
Smoke tests completed. 144 look OK, 5 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File
test_LoginApiDomain Error 7.14 test_accounts.py
ContextSuite context=TestListIdsParams>:teardown Error 1.14 test_list_ids_parameter.py
test_01_deployVMInSharedNetwork Error 278.93 test_network.py
test_01_snapshot_root_disk Error 5.94 test_snapshots.py
test_02_list_snapshots_with_removed_data_store Error 47.68 test_snapshots.py
test_02_list_snapshots_with_removed_data_store Error 47.69 test_snapshots.py
ContextSuite context=TestSnapshotStandaloneBackup>:teardown Error 32.91 test_snapshots.py
test_01_snapshot_usage Error 21.67 test_usage.py
test_01_vpn_usage Error 1.09 test_usage.py

@boring-cyborg boring-cyborg bot added component:integration-test Python Warning... Python code Ahead! labels Feb 27, 2026
@codecov
Copy link

codecov bot commented Feb 27, 2026

Codecov Report

❌ Patch coverage is 70.27027% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 17.62%. Comparing base (5caf6cd) to head (738ec86).
⚠️ Report is 12 commits behind head on 4.22.

Files with missing lines Patch % Lines
...c/main/java/com/cloud/utils/ssh/SSHKeysHelper.java 70.27% 9 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               4.22   #12708      +/-   ##
============================================
+ Coverage     17.60%   17.62%   +0.01%     
- Complexity    15659    15678      +19     
============================================
  Files          5917     5917              
  Lines        531394   531428      +34     
  Branches      64970    64970              
============================================
+ Hits          93575    93649      +74     
+ Misses       427269   427227      -42     
- Partials      10550    10552       +2     
Flag Coverage Δ
uitests 3.70% <ø> (-0.01%) ⬇️
unittests 18.69% <70.27%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@weizhouapache
Copy link
Member Author

@blueorangutan package

@blueorangutan
Copy link

@weizhouapache a [SL] Jenkins job has been kicked to build packages. It will be bundled with KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan
Copy link

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 16975

@weizhouapache
Copy link
Member Author

@blueorangutan test

@blueorangutan
Copy link

@weizhouapache a [SL] Trillian-Jenkins test job (ol8 mgmt + kvm-ol8) has been kicked to run smoke tests

@blueorangutan
Copy link

[SF] Trillian Build Failed (tid-15544)

@blueorangutan
Copy link

[SF] Trillian Build Failed (tid-15545)

@blueorangutan
Copy link

[SF] Trillian test result (tid-15546)
Environment: kvm-ol8 (x2), zone: Advanced Networking with Mgmt server ol8
Total time taken: 52378 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr12708-t15546-kvm-ol8.zip
Smoke tests completed. 143 look OK, 6 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File
test_LoginApiDomain Error 5.56 test_accounts.py
ContextSuite context=TestClusterDRS>:setup Error 0.00 test_cluster_drs.py
ContextSuite context=TestListIdsParams>:teardown Error 1.10 test_list_ids_parameter.py
test_01_snapshot_root_disk Error 5.81 test_snapshots.py
test_02_list_snapshots_with_removed_data_store Error 48.49 test_snapshots.py
test_02_list_snapshots_with_removed_data_store Error 48.50 test_snapshots.py
ContextSuite context=TestSnapshotStandaloneBackup>:teardown Error 25.28 test_snapshots.py
test_01_snapshot_usage Error 22.64 test_usage.py
test_01_vpn_usage Error 1.07 test_usage.py
test_01_redundant_vpc_site2site_vpn Failure 365.16 test_vpc_vpn.py

@sureshanaparti
Copy link
Contributor

@weizhouapache is this ready for review? also, check copilot comments if relevant.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +123 to +124
assertTrue(privateKey.contains("BEGIN RSA PRIVATE KEY"));
assertTrue(privateKey.contains("END RSA PRIVATE KEY"));
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test asserts "BEGIN RSA PRIVATE KEY" and "END RSA PRIVATE KEY", but keyPair.getPrivate().getEncoded() returns PKCS#8 format bytes. Using the PEM type "RSA PRIVATE KEY" (PKCS#1 header) with PKCS#8 content is incorrect. If the production code is fixed to use "PRIVATE KEY" (the correct PKCS#8 header), these assertions will need to be updated to check for "BEGIN PRIVATE KEY" and "END PRIVATE KEY" instead. Alternatively, if PKCS#1 is chosen, the production code needs to convert the key format accordingly.

Suggested change
assertTrue(privateKey.contains("BEGIN RSA PRIVATE KEY"));
assertTrue(privateKey.contains("END RSA PRIVATE KEY"));
assertTrue(privateKey.contains("BEGIN PRIVATE KEY"));
assertTrue(privateKey.contains("END PRIVATE KEY"));

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +157
final PemObject pemObject = new PemObject("RSA PRIVATE KEY", keyPair.getPrivate().getEncoded());
final StringWriter sw = new StringWriter();
try (final PemWriter pw = new PemWriter(sw)) {
pw.writeObject(pemObject);
}
return sw.toString();
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PEM type "RSA PRIVATE KEY" denotes PKCS#1 format, but keyPair.getPrivate().getEncoded() returns PKCS#8 (DER-encoded PrivateKeyInfo) bytes. This mismatch means the generated PEM file will have a PKCS#1 header wrapping PKCS#8 content, which will cause failures in tools (like OpenSSH) that try to parse it.

The codebase already handles this correctly in CertUtils.privateKeyToPem() (at utils/src/main/java/org/apache/cloudstack/utils/security/CertUtils.java:134) which uses "PRIVATE KEY" (PKCS#8 header) with key.getEncoded().

There are two correct approaches:

  1. Use "PRIVATE KEY" as the PEM type (PKCS#8 format), matching what CertUtils.privateKeyToPem() does, or better yet, just call CertUtils.privateKeyToPem(keyPair.getPrivate()) directly.
  2. Convert the key to PKCS#1 format using BouncyCastle's PrivateKeyInfo and RSAPrivateKey before wrapping it with the "RSA PRIVATE KEY" header.

Note: Option 1 would change the output format from PKCS#1 (which JSch previously produced) to PKCS#8. If downstream consumers specifically expect PKCS#1, option 2 would be needed for backward compatibility. However, most modern SSH tools accept both formats.

Suggested change
final PemObject pemObject = new PemObject("RSA PRIVATE KEY", keyPair.getPrivate().getEncoded());
final StringWriter sw = new StringWriter();
try (final PemWriter pw = new PemWriter(sw)) {
pw.writeObject(pemObject);
}
return sw.toString();
return CertUtils.privateKeyToPem(keyPair.getPrivate());

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

4 participants